<?php

/**
 * RaiseTest abstract class
 * represents a test unit with appropriate testing tools
 *
 * @author Sam-Mauris Yong / hellclanner at live dot com
 * @license http://www.opensource.org/licenses/bsd-license New BSD License
 * @abstract
 * @package Raise.UnitTest
 * @version 1.4
 */
abstract class RaiseTest {
    /**
     * Test result of this Test Unit.
     * @var RaiseTestResult
     * @static
     */
    private static $result;
    
    /**
     * The output buffer
     * @var array
     * @static
     */
    private static $outputBuffer;
    
    /**
     * The current unit and method name
     * @var string
     * @static
     */
    private static $currentModule;

    /**
     * Add a message to the test result
     * @param string $message The message
     * @static
     * @final
     */
    protected final static function addMessage($message){
        if(self::$result != null){
            if($message){
                self::$result->messages[] = 'At ' . self::$currentModule . "\n" . $message;
            }else{
                self::addWarning('Message log is left empty.');
            }
        }
    }

    /**
     * Add an error message to the test result
     * @param string $error The error message
     * @static
     * @final
     */
    protected final static function addError($error){
        if(self::$result != null){
            if($error){
                self::$result->errors[] = 'At ' . self::$currentModule . "\n" . $error;
            }else{
                self::addWarning('Error message is left empty.');
            }
        }
    }

    /**
     * Add a warning message to the test result
     * @param string $error The warning message
     * @static
     * @final
     */
    protected final static function addWarning($warning){
        if(self::$result != null){
            if($warning){
                self::$result->warnings[] = 'At ' . self::$currentModule . "\n" . $warning;
            }else{
                self::addWarning('Warning message is left empty.');
            }
        }
    }

    /**
     * Assert a value if it is false
     * @param mixed $value The value to assert
     * @static
     * @final
     */
    protected final static function assert($value){
        $result = (bool)$value;
        self::$result->totalTestCount++;
        if($result){
            self::$result->successfulTestCount++;
        }else{
            $tree = debug_backtrace();
            $at = ' at ' . $tree[0]['file'] . ' [line ' . $tree[0]['line'] . ']';
            self::addWarning('Assertion failed' . $at . ': ' . var_export($value, true));
        }
        return $result;
    }

    /**
     * Assert a PHP code expression if it is false
     * @param string $value The PHP code to assert
     * @static
     * @final
     */
    protected final static function assertExpression($value){
        $result = eval($value);
        self::$result->totalTestCount++;
        if($result){
            self::$result->successfulTestCount++;
        }else{
            $tree = debug_backtrace();
            $at = ' at ' . $tree[0]['file'] . ' [line ' . $tree[0]['line'] . ']';
            self::addWarning('Assertion failed' . $at . ': ' . var_export($value, true));
        }
        return $result;
    }

    /**
     * Assert a value to be TRUE
     * @param mixed $value The value to assert
     * @static
     * @final
     */
    protected final static function assertTrue($value){
        $result = ($value === true);
        self::$result->totalTestCount++;
        if($result){
            self::$result->successfulTestCount++;
        }else{
            $tree = debug_backtrace();
            $at = ' at ' . $tree[0]['file'] . ' [line ' . $tree[0]['line'] . ']';
            self::addWarning('Assert to TRUE failed' . $at . ': ' . var_export($value, true));
        }
        return $result;
    }

    /**
     * Assert a value to be FALSE
     * @param mixed $value The value to assert
     * @static
     * @final
     */
    protected final static function assertFalse($value){
        $result = ($value === false);
        self::$result->totalTestCount++;
        if($result){
            self::$result->successfulTestCount++;
        }else{
            $tree = debug_backtrace();
            $at = ' at ' . $tree[0]['file'] . ' [line ' . $tree[0]['line'] . ']';
            self::addWarning('Assert to FALSE failed' . $at . ': ' . var_export($value, true));
        }
        return $result;
    }

    /**
     * Assert a value if it is not a certain data type
     * @param mixed $value The value to assert
     * @static
     * @final
     */
    protected final static function assertType($variable, $type){
        $result = (gettype($variable) === $type);
        self::$result->totalTestCount++;
        if($result){
            self::$result->successfulTestCount++;
        }else{
            $tree = debug_backtrace();
            $at = ' at ' . $tree[0]['file'] . ' [line ' . $tree[0]['line'] . ']';
            self::addWarning('Assert Variable Data Type equivalence failed' . $at . ': ' . var_export(gettype($variable), true) . ' === ' . var_export($type, true));
        }
        return $result;
    }

    /**
     * Assert if a class exists
     * @param string|array $name Name of the class to check
     * @static
     * @final
     */
    protected final static function assertClassExists($name){
        if(is_array($name)){
            foreach($name as $a){
                self::assertClassExists($a);
            }
        }else{
            $result = (class_exists($name));
            self::$result->totalTestCount++;
            if($result){
                self::$result->successfulTestCount++;
            }else{
                $tree = debug_backtrace();
                $at = ' at ' . $tree[0]['file'] . ' [line ' . $tree[0]['line'] . ']';
                self::addWarning('Assert class existence failed' . $at . ': Class \'' . $name . '\' does not exists.');
            }
            return $result;
        }
    }

    /**
     * Assert if a class is a final class
     * @param string $name Name of the class to check
     * @static
     * @final
     */
    protected final static function assertClassIsFinal($name){
        $reflection = new ReflectionClass($name);
        $result = $reflection->isFinal();
        self::$result->totalTestCount++;
        if($result){
            self::$result->successfulTestCount++;
        }else{
            $tree = debug_backtrace();
            $at = ' at ' . $tree[0]['file'] . ' [line ' . $tree[0]['line'] . ']';
            self::addWarning('Assert class final failed' . $at . ': Class \'' . $name . '\' is not a final class.');
        }
        return $result;
    }

    /**
     * Assert if a class is an abstract class
     * @param string $name Name of the class to check
     * @static
     * @final
     */
    protected final static function assertClassIsAbstract($name){
        $reflection = new ReflectionClass($name);
        $result = $reflection->isAbstract();
        self::$result->totalTestCount++;
        if($result){
            self::$result->successfulTestCount++;
        }else{
            $tree = debug_backtrace();
            $at = ' at ' . $tree[0]['file'] . ' [line ' . $tree[0]['line'] . ']';
            self::addWarning('Assert class abstraction failed' . $at . ': Class \'' . $name . '\' is not an abstract class.');
        }
        return $result;
    }

    /**
     * Assert if a class extends another class
     * @param string $name Name of the class to check
     * @param string $base Name of the parent class that this class extends from
     * @static
     * @final
     */
    protected final static function assertClassExtends($name, $base){
        $reflection = new ReflectionClass($name);
        $a = $reflection->getParentClass();
        $result = ($a && $a->getName() == $base);
        self::$result->totalTestCount++;
        if($result){
            self::$result->successfulTestCount++;
        }else{
            $tree = debug_backtrace();
            $at = ' at ' . $tree[0]['file'] . ' [line ' . $tree[0]['line'] . ']';
            self::addWarning('Assert class extends parent class failed' . $at . ': Class \'' . $name . '\' does not extend class \'' . $base . '\'.');
        }
        return $result;
    }

    /**
     * Assert if a class implements an interface
     * @param string $name Name of the class to check
     * @param string $interface Name of the interface to check if the class implements
     * @static
     * @final
     */
    protected final static function assertClassImplements($name, $interface){
        $reflection = new ReflectionClass($name);
        $result = $reflection->implementsInterface($interface);
        self::$result->totalTestCount++;
        if($result){
            self::$result->successfulTestCount++;
        }else{
            $tree = debug_backtrace();
            $at = ' at ' . $tree[0]['file'] . ' [line ' . $tree[0]['line'] . ']';
            self::addWarning('Assert class implement interface failed' . $at . ': Class \'' . $name . '\' does not implement interface \'' . $interface . '\'.');
        }
        return $result;
    }

    /**
     * Assert if an interface exists
     * @param string $name Name(s) of the interface to check
     * @static
     * @final
     */
    protected final static function assertInterfaceExists($name){
        if(is_array($name)){
            foreach($name as $a){
                self::assertInterfaceExists($a);
            }
        }else{
            $result = (interface_exists($name));
            self::$result->totalTestCount++;
            if($result){
                self::$result->successfulTestCount++;
            }else{
                $tree = debug_backtrace();
                $at = ' at ' . $tree[0]['file'] . ' [line ' . $tree[0]['line'] . ']';
                self::addWarning('Assert interface existence failed' . $at . ': Interface \'' . $name . '\' does not exists.');
            }
            return $result;
        }
    }

    /**
     * Assert if a function exists
     * @param string|array $name Name of the function to check
     * @static
     * @final
     */
    protected final static function assertFunctionExists($name){
        if(is_array($name)){
            foreach($name as $a){
                self::assertFunctionExists($a);
            }
        }else{
            $result = (function_exists($name));
            self::$result->totalTestCount++;
            if($result){
                self::$result->successfulTestCount++;
            }else{
                $tree = debug_backtrace();
                $at = ' at ' . $tree[0]['file'] . ' [line ' . $tree[0]['line']  . ']';
                self::addWarning('Assert function existence failed' . $at . ': Function \'' . $name . '\' does not exists.');
            }
            return $result;
        }
    }

    /**
     * Assert if a constant is defined
     * @param string|array $name Name(s) of the constant to check
     * @static
     * @final
     */
    protected final static function assertConstantDefined($name){
        if(is_array($name)){
            foreach($name as $a){
                self::assertConstantDefined($a);
            }
        }else{
            $result = (defined($name));
            self::$result->totalTestCount++;
            if($result){
                self::$result->successfulTestCount++;
            }else{
                $tree = debug_backtrace();
                $at = ' at ' . $tree[0]['file'] . ' [line ' . $tree[0]['line'] . ']';
                self::addWarning('Assert constant definition failed' . $at . ': Constant \'' . $name . '\' does not exists.');
            }
            return $result;
        }
    }

    /**
     * Assert if a class has a specific method
     * @param string $class Name of the class to check
     * @param string|array $method Name(s) of the method to check
     * @static
     * @final
     */
    protected final static function assertClassMethod($class, $method){
        if(is_array($method)){
            foreach($method as $m){
                self::assertClassMethod($class, $m);
            }
        }else{
            $result = in_array($method, get_class_methods($class));
            self::$result->totalTestCount++;
            if($result){
                self::$result->successfulTestCount++;
            }else{
                $tree = debug_backtrace();
                $at = ' at ' . $tree[0]['file'] . ' [line ' . $tree[0]['line']  . ']';
                self::addWarning('Assert class method existance failed' . $at . ': Class \'' . $class . '\' does not have a method named \'' . $method . '\'.');
            }
            return $result;
        }
    }

    /**
     * Assert if a specific file exists
     * @param string $filename Path name to the file to check
     * @static
     * @final
     */
    protected final static function assertFileExist($filename){
        $result = file_exists($filename);
        self::$result->totalTestCount++;
        if($result){
            self::$result->successfulTestCount++;
        }else{
            $tree = debug_backtrace();
            $at = ' at ' . $tree[0]['file'] . ' [line ' . $tree[0]['line']  . ']';
            self::addWarning('Assert file existance failed' . $at . ': File \''.$filename.'\' is not found.');
        }
        return $result;
    }

    /**
     * Assert if a specific file does not
     * @param string $filename Path name to the file to check
     * @static
     * @final
     */
    protected final static function assertFileNotExist($filename){
        $result = !file_exists($filename);
        self::$result->totalTestCount++;
        if($result){
            self::$result->successfulTestCount++;
        }else{
            $tree = debug_backtrace();
            $at = ' at ' . $tree[0]['file'] . ' [line ' . $tree[0]['line']  . ']';
            self::addWarning('Assert file non-existance failed' . $at . ': File \''.$filename.'\' is found.');
        }
        return $result;
    }

    /**
     * Assert two values to be not equal
     * @param mixed $value1 The first value
     * @param mixed $value2 The second value
     * @static
     * @final
     */
    protected final static function assertNotEqual($value1, $value2){
        $result = ($value1 != $value2);
        self::$result->totalTestCount++;
        if($result){
            self::$result->successfulTestCount++;
        }else{
            $tree = debug_backtrace();
            $at = ' at ' . $tree[0]['file'] . ' [line ' . $tree[0]['line']  . ']';
            self::addWarning('Assert inequivalence failed' . $at . ': ' . var_export($value1, true) . ' != ' . var_export($value2, true));
        }
        return $result;
    }

    /**
     * Assert two values to be equal
     * @param mixed $value1 The first value
     * @param mixed $value2 The second value
     * @static
     * @final
     */
    protected final static function assertEqual($value1, $value2){
        $result = ($value1 == $value2);
        self::$result->totalTestCount++;
        if($result){
            self::$result->successfulTestCount++;
        }else{
            $tree = debug_backtrace();
            $at = ' at ' . $tree[0]['file'] . ' [line ' . $tree[0]['line']  . ']';
            self::addWarning('Assert equivalence failed' . $at . ': ' . var_export($value1, true) . ' == ' . var_export($value2, true));
        }
        return $result;
    }

    /**
     * Assert an array to have an element
     * @param mixed $array The array to assert
     * @param mixed $element The element to find
     * @static
     * @final
     */
    protected final static function assertArrayHasElement($array, $element){
        $result = in_array($element, $array);
        self::$result->totalTestCount++;
        if($result){
            self::$result->successfulTestCount++;
        }else{
            $tree = debug_backtrace();
            $at = ' at ' . $tree[0]['file'] . ' [line ' . $tree[0]['line']  . ']';
            self::addWarning('Assert array has element failed' . $at . ': ' . var_export($element, true) . ' -> ' . var_export($array, true));
        }
        return $result;
    }

    /**
     * Assert an array to have an key
     * @param mixed $array The array to assert
     * @param mixed $key The key to find
     * @static
     * @final
     */
    protected final static function assertArrayHasKey($array, $key){
        $result = array_key_exists($key, $array);
        self::$result->totalTestCount++;
        if($result){
            self::$result->successfulTestCount++;
        }else{
            $tree = debug_backtrace();
            $at = ' at ' . $tree[0]['file'] . ' [line ' . $tree[0]['line']  . ']';
            self::addWarning('Assert array has key failed' . $at . ': ' . var_export($key, true) . ' -> ' . var_export($array, true));
        }
        return $result;
    }

    /**
     * Assert a value to be NULL using the '===' operator
     * @param mixed $value1 The first value
     * @static
     * @final
     */
    protected final static function assertNull($value){
        $result = ($value === null);
        self::$result->totalTestCount++;
        if($result){
            self::$result->successfulTestCount++;
        }else{
            $tree = debug_backtrace();
            $at = ' at ' . $tree[0]['file'] . ' [line ' . $tree[0]['line']  . ']';
            self::addWarning('Assert null failed' . $at . ': ' . var_export($value, true) . ' === NULL');
        }
        return $result;
    }

    /**
     * Assert two values to be strictly equal using the '===' operator
     * @param mixed $value1 The first value
     * @param mixed $value2 The second value
     * @static
     * @final
     */
    protected final static function assertStrictEqual($value1, $value2){
        $result = ($value1 === $value2);
        self::$result->totalTestCount++;
        if($result){
            self::$result->successfulTestCount++;
        }else{
            $tree = debug_backtrace();
            $at = ' at ' . $tree[0]['file'] . ' [line ' . $tree[0]['line']  . ']';
            self::addWarning('Assert strict equivalence failed' . $at . ': ' . var_export($value1, true) . ' === ' . var_export($value2, true));
        }
        return $result;
    }

    /**
     * Assert if an object is an instance of a class
     * @param object $variable
     * @param mixed $type
     * @return boolean
     */
    protected final static function assertObjectClass($variable, $type){
        $result = $variable instanceof $type;
        self::$result->totalTestCount++;
        if($result){
            self::$result->successfulTestCount++;
        }else{
            $tree = debug_backtrace();
            $at = ' at ' . $tree[0]['file'] . ' [line ' . $tree[0]['line']  . ']';
            self::addWarning('Assert to class "'.$type.'" failed' . $at . ': ' . var_export($variable, true));
        }
        return $result;
    }
    
    protected final static function startBuffer($bufferName){
        self::$outputBuffer[$bufferName] = '';
        ob_start();
    }
    
    protected final static function stopBuffer($bufferName){
        self::$outputBuffer[$bufferName] = ob_get_contents();
        ob_end_clean();
    }

    /**
     * Run the test and return the result of the test
     * @return RaiseTestResult
     * @static
     * @final
     */
    public final static function run($class){
        
        self::$result = new RaiseTestResult();
        

        $totalTime = 0;
        $memoryUse = 0;

        $methods = get_class_methods($class);
        $load = in_array('load', $methods);
        if($load){
            call_user_func(array($class, 'load'));
        }
        foreach($methods as $method){
            if(strtolower(substr($method, 0, 4)) == 'test'){
                self::$outputBuffer = array();
                ob_start();
                $startTime = self::microtime();
                $a = memory_get_usage();
                self::$currentModule = $class . '::' . $method;
                try{
                    call_user_func(array($class, $method));
                }catch(Exception $e){
                    self::addError('[RaiseTest] Uncaught exception: ' . $e->__toString());
                }
                $memoryUse += memory_get_usage() - $a;
                $totalTime += self::microtime() - $startTime;
                unset($startTime);
                    
                foreach(self::$outputBuffer as $name => $message){
                    self::addMessage('==========[' . $name . ']==========' . "\n" . $message . "\n" . '==========[' . $name . ']==========');
                }
                ob_end_clean();
            }
        }
        $unload = in_array('unload', $methods);
        if($unload){
            call_user_func(array($class, 'unload'));
        }

        self::$result->timeTaken = $totalTime;
        self::$result->memoryUse = $memoryUse;
        unset($totalTime);

        return self::$result;
    }


    /**
     * Get the current time in microtime;
     * @return double
     * @static
     */
    private static function microtime(){
        list($usec, $sec) = explode(' ', microtime());
        return ((double)$usec + (double)$sec);
    }
};

if( !function_exists('memory_get_usage') ) 
{ 
    function memory_get_usage() 
    { 
        //If its Windows 
        //Tested on Win XP Pro SP2. Should work on Win 2003 Server too 
        //Doesn't work for 2000 
        //If you need it to work for 2000 look at http://us2.php.net/manual/en/function.memory-get-usage.php#54642 
        if ( substr(PHP_OS,0,3) == 'WIN') 
        { 
               if ( substr( PHP_OS, 0, 3 ) == 'WIN' ) 
                { 
                    $output = array(); 
                    exec( 'tasklist /FI "PID eq ' . getmypid() . '" /FO LIST', $output ); 
        
                    return preg_replace( '/[\D]/', '', $output[5] ) * 1024; 
                } 
        }else 
        { 
            //We now assume the OS is UNIX 
            //Tested on Mac OS X 10.4.6 and Linux Red Hat Enterprise 4 
            //This should work on most UNIX systems 
            $pid = getmypid(); 
            exec("ps -eo%mem,rss,pid | grep $pid", $output); 
            $output = explode("  ", $output[0]); 
            //rss is given in 1024 byte units 
            return $output[1] * 1024; 
        } 
    } 
} 